﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Lite.Common;

namespace Lite.Net
{
    internal sealed class BufferPool
    {
        private readonly int _segmentSize;
        private readonly List<Page> _pages = new List<Page>();

        private readonly object _poolLockObject = new object();

        public BufferPool(int segmentSize)
        {
            Debug.Assert(segmentSize > 0, "Segment size must be greater than 0");

            _segmentSize = segmentSize;
        }

        public IBufferHandle RequestBuffer(int minSize)
        {
            int bufferSize = _segmentSize * (int)Math.Ceiling((double)minSize / (double)_segmentSize);

            return new BufferHandle(GetPageOfSize(bufferSize));
        }

        private Page GetPageOfSize(int bufferSize)
        {
            Page existingPage = SearchForFreePage(bufferSize);

            if (existingPage != null)
            {
                return existingPage;
            }

            Page newPage = new Page(bufferSize);

            lock (_poolLockObject)
            {
                _pages.Add(newPage);
            }

            return newPage;
        }

        private Page SearchForFreePage(int bufferSize)
        {
            lock (_poolLockObject)
            {
                foreach (Page page in _pages)
                {
                    if (!page.IsLocked && page.GetBuffer().Length >= bufferSize && !page.IsSuspect)
                    {
                        return page;
                    }
                }
            }

            return null;
        }

        #region BufferHandle class

        private sealed class BufferHandle
            : IBufferHandle
        {
            private readonly Page _bufferPage;
            private bool _isDisposed;

            public BufferHandle(Page bufferPage)
            {
                bufferPage.Lock();

                _bufferPage = bufferPage;
            }

            ~BufferHandle()
            {
                Dispose(false);
            }

            public byte[] GetBuffer()
            {
                if (_isDisposed)
                    throw new ObjectDisposedException(null);
                else
                    return _bufferPage.GetBuffer();
            }

            private void Dispose(bool isDisposing)
            {
                if (!_isDisposed)
                {
                    if (isDisposing)
                    {
                        _bufferPage.Release();
                    }
                    else
                    {
                        _bufferPage.MarkAsSuspect();
                    }

                    _isDisposed = true;
                }
            }

            #region IDisposable Members

            public void Dispose()
            {
                Dispose(true);

                GC.SuppressFinalize(this);
            }

            #endregion
        }

        #endregion

        #region Page class

        private sealed class Page
        {
            private readonly object _isLockedLockObject = new object();
            private readonly byte[] _buffer;

            public Page(int bufferSize)
            {
                Debug.Assert(bufferSize > 0, "Buffer size must be greater than 0");

                _buffer = new byte[bufferSize];
            }

            public byte[] GetBuffer()
            {
                return _buffer;
            }

            public bool IsLocked
            {
                get;
                private set;
            }

            public bool IsSuspect
            {
                get;
                private set;
            }

            public void Lock()
            {
                lock (_isLockedLockObject)
                {
                    if (IsLocked)
                        throw new LiteException("BufferPage is already locked.");
                    else
                        IsLocked = true;
                }
            }

            public void MarkAsSuspect()
            {
                IsSuspect = true;
            }

            public void Release()
            {
                lock (_isLockedLockObject)
                {
                    if (!IsLocked)
                        throw new LiteException("BufferPage is not locked.");
                    else
                        IsLocked = false;
                }
            }
        }

        #endregion
    }

    public interface IBufferHandle
        : IDisposable
    {
        byte[] GetBuffer();
    }
}
